import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringBufferInputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;

import net.jxta.discovery.DiscoveryEvent;
import net.jxta.discovery.DiscoveryListener;
import net.jxta.discovery.DiscoveryService;
import net.jxta.document.Advertisement;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.endpoint.Message.ElementIterator;
import net.jxta.peer.PeerID;
import net.jxta.pipe.PipeMsgEvent;
import net.jxta.platform.NetworkManager.ConfigMode;
import net.jxta.protocol.DiscoveryResponseMsg;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.util.JxtaBiDiPipe;
import utils.ApplicationState;
import utils.JXTAMessageUtils;
import utils.ReflectionHelper;
import utils.SerializeHelper;
import utils.WorkStatus;
import advs.ServiceAdvertisement;
import advs.ServicePublisherAdvertisement;

public class ServiceFinder extends JxtaApplication
{

	String solverName = "*Math*";
	ArrayList<String> advIDs = new ArrayList<String>();
	HashMap<String, ServicePublisherAdvertisement> servicePublisherAdvs = new HashMap<String, ServicePublisherAdvertisement>();

	HashMap<String, JxtaBiDiPipe> bidiPipes = new HashMap<String, JxtaBiDiPipe>();

	String[] words;
	int counter = 0;
	long startTime;
	long endTime;
	int responsesReceived = 0;

	public ServiceFinder()
	{
		super("Service Finder");
		startTime = System.currentTimeMillis();
	}

	@Override
	public void Start()
	{
		try
		{

			FileReader reader = new FileReader("words.txt");
			BufferedReader bufferedReader = new BufferedReader(reader);
			String line = bufferedReader.readLine();
			words = line.split(" ");
		}
		catch (Exception exc)
		{
			Log("ERROR:" + exc.getMessage());
		}

		Initialize(ConfigMode.EDGE, "SolverFinder", false);
		try
		{

			Enumeration<Advertisement> advs = getDiscoveryService()
					.getLocalAdvertisements(DiscoveryService.ADV,
							ServiceAdvertisement.ServiceNameTag, solverName);
			ProcessAdvertisements(advs);
			getDiscoveryService().getRemoteAdvertisements(null,
					DiscoveryService.ADV, ServiceAdvertisement.ServiceNameTag,
					solverName, 100);
		}
		catch (Exception e)
		{
			Log("ERROR1:" + e.getMessage());
		}
	}

	@Override
	public void discoveryEvent(DiscoveryEvent event)
	{
		DiscoveryResponseMsg response = event.getResponse();
		Enumeration<Advertisement> advs = response.getAdvertisements();
		ProcessAdvertisements(advs);
	}

	private void ProcessAdvertisements(Enumeration<Advertisement> advs)
	{
		while (advs.hasMoreElements())
		{
			Advertisement genericAdv = advs.nextElement();
			String advType = genericAdv.getAdvType();
			if (advType == ServiceAdvertisement.getAdvertisementType())
			{
				ServiceAdvertisement adv = (ServiceAdvertisement) genericAdv;
				Log("SA found:" + adv.getId());

				String advID = adv.getSolverAdvID().toString();

				if (!advIDs.contains(advID))
				{
					advIDs.add(advID);
					Log("Searching SPA for this SA:" + advID);
					getDiscoveryService().getRemoteAdvertisements(null,
							DiscoveryService.ADV,
							ServicePublisherAdvertisement.IDTag, advID, 1);

				}
				else
				{
					Log("SPA is already being searched:" + advID);
				}

			}

			if (genericAdv.getAdvType() == ServicePublisherAdvertisement
					.getAdvertisementType())
			{
				ServicePublisherAdvertisement tempAdv = (ServicePublisherAdvertisement) genericAdv;
				String tempSolverAdvID = tempAdv.getId();
				if (advIDs.contains(tempSolverAdvID))
				{
					Log("SPA found");
					ServicePublisherAdvertisement servicePublisherAdv = (ServicePublisherAdvertisement) genericAdv;

					servicePublisherAdvs.put(servicePublisherAdv.getPipeAdvID()
							.toString(), servicePublisherAdv);

					Log("Searching for pipe:" + "\\r\\n"
							+ servicePublisherAdv.getPipeAdvID());

					getDiscoveryService().getRemoteAdvertisements(null,
							DiscoveryService.ADV, PipeAdvertisement.IdTag,
							servicePublisherAdv.getPipeAdvID(), 1);

				}
			}

			if (genericAdv.getAdvType() == PipeAdvertisement
					.getAdvertisementType())
			{
				try
				{
					PeerID peerID = null;
					PipeAdvertisement pipeTemp = (PipeAdvertisement) genericAdv;
					if (servicePublisherAdvs.containsKey(pipeTemp.getPipeID()
							.toString())
							&& !bidiPipes.containsKey(pipeTemp.getPipeID()
									.toString()))
					{
						try
						{
							peerID = PeerID.create(new URI(servicePublisherAdvs
									.get(pipeTemp.getPipeID().toString())
									.getPeerID()));
						}
						catch (Exception e)
						{
							Log("ERROR" + e.getMessage());
						}
						Log("Establishing connection with"
								+ pipeTemp.getPipeID());

						JxtaBiDiPipe b = new JxtaBiDiPipe();
						try
						{
							bidiPipes.put(pipeTemp.getPipeID().toString(), b);
							System.out.println("ADDED:"
									+ pipeTemp.getPipeID().toString());
							b.connect(getGroup(), null, pipeTemp, 30 * 1000,
									this, true);
						}
						catch (Exception exc)
						{

							Log("Conexiune esuata cu pipe-ul "
									+ pipeTemp.getPipeID().toString() + " "
									+ pipeTemp.getID());
							bidiPipes.remove("REMOVED:"
									+ pipeTemp.getPipeID().toString());
							Log(pipeTemp.getPipeID().toString());
						}
						if (b.isBound())
						{
							Log("Connection succesfull" + pipeTemp.getPipeID());

						}
					}
					else
					{
						Log("s-a evitat cautarea multipla a unui adv");
					}

				}
				catch (Exception e)
				{
					Log("ERROR:" + e.getMessage());
				}
			}
		}

	}

	private void SendMessageToSolver(WorkStatus status, String pipeAdvIDString)
	{

		if (pipeAdvIDString.compareTo("") == 0)
		{
			Log("ERROR:Pipe adv id not found");
			return;
		}
		JxtaBiDiPipe bidi = bidiPipes.get(pipeAdvIDString);
		Log("Sending work using pipe : "
				+ bidi.getPipeAdvertisement().getID().toString());
		try
		{
			Message message = new Message();
			message.addMessageElement(new StringMessageElement(
					JXTAMessageUtils.WORKSTATUS, status.toString(), null));
			if (status == status.HasNext)
			{

				ServicePublisherAdvertisement publisherAdv = servicePublisherAdvs
						.get(bidi.getPipeAdvertisement().getPipeID().toString());
				Object obj = ReflectionHelper.GetObjectFromAdv(publisherAdv);

				Object param1 = words[counter];

				String parameters = SerializeHelper.GetStringFromObject(param1);

				// System.out
				// .println("Starea obiectului inainte de a fi executat de
				// solver");
				// System.out.println(obj.toString());

				message.addMessageElement(new StringMessageElement(
						JXTAMessageUtils.ADV_ID, publisherAdv.getId(), null));

				message.addMessageElement(new StringMessageElement(
						JXTAMessageUtils.PATH, publisherAdv.getServicePath(),
						null));

				message.addMessageElement(new StringMessageElement(
						JXTAMessageUtils.PARAMETERS, parameters, null));

				message.addMessageElement(new StringMessageElement(
						JXTAMessageUtils.OBJECT, SerializeHelper
								.GetStringFromObject(obj), null));
			}

			if (!bidi.isBound())
			{
				Log("BiDiPipe is not connected.The object cannot be sent");
				return;
			}
			bidi.sendMessage(message);

		}
		catch (Exception e)
		{
			Log("ERROR:" + e.getMessage());
		}

	}

	@Override
	public void pipeMsgEvent(PipeMsgEvent event)
	{

		Log("Receiving message from solver:" + event.getPipeID());
		Message message = event.getMessage();

		ElementIterator elementIterator = message.getMessageElements();

		String pipeIdString = "";

		while (elementIterator.hasNext())
		{
			MessageElement element = elementIterator.next();

			if (element.getElementName().compareTo(JXTAMessageUtils.PIPEID) == 0)
			{
				pipeIdString = element.toString();
			}

			if (element.getElementName().compareTo(JXTAMessageUtils.ACTION) == 0)
			{
				try
				{
					ApplicationState state = ApplicationState.valueOf(element
							.toString());
					Log("solvers says it is :" + state);
					if (state == ApplicationState.Available)
					{
						responsesReceived = 0;
						SendMessageToSolver(WorkStatus.HasNext,pipeIdString);
						return;
					}
				}

				catch (Exception exc)
				{
					Log("ERROR:most likely on the found peer;"
							+ exc.getMessage());
				}
			}

			if (element.getElementName().compareTo(JXTAMessageUtils.RESULT) == 0)
			{
				try
				{
					Object obj = SerializeHelper.GetObjectFromString(element
							.toString());
					Log("object received:" + obj.toString());
					responsesReceived++;
					Log("Objects received until now:" + responsesReceived);
				}

				catch (Exception exc)
				{
					Log("ERROR:" + exc.getMessage());
				}
			}

		}

		Log("Objects sent until now:" + (counter+1));
		if (counter < words.length - 1)
		{
			counter++;
			SendMessageToSolver(WorkStatus.HasNext,pipeIdString);
		}
		else
		{
			if (responsesReceived == counter+1)
			{

				endTime = System.currentTimeMillis();
				long duration = endTime - startTime;
				Log(duration + " in milliseconds");
				Log("To process all " + words.length + " words it took");
				int minutes = (int) (duration / (1000 * 60));
				duration = duration % (1000 * 60);
				Log(minutes + " minutes");
				int seconds = (int) (duration / (1000));
				duration = duration % (1000);
				Log(seconds + " seconds");
				Log(duration + " milliseconds");
				SendMessageToSolver(WorkStatus.Done,pipeIdString);
			}
		}

	}

	public String getSolverName()
	{
		return solverName;
	}

	public void setSolverName(String serviceName)
	{
		this.solverName = serviceName;
	}

}
